﻿using PersistentObject.Common.Criteria;
using PersistentObject.Common.Exceptions;
using PersistentObject.Common.Schema;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PersistentObject.Common
{
    public class BaseCriteriaCreator
    {
        public Table MainTable { get; set; }

        public BaseDialect Dialect { get; set; }

        public BaseCriteriaCreator(Table mainTable, BaseDialect dialect)
        {
            MainTable = mainTable;
            Dialect = dialect;
        }

        protected virtual string GetComparisonScript(BinaryComparison compare)
        {
            switch (compare)
            {
                case BinaryComparison.Equal:
                    return "=";
                case BinaryComparison.NotEqual:
                    return "!=";
                case BinaryComparison.GreaterThan:
                    return ">";
                case BinaryComparison.GreaterThanOrEqual:
                    return ">=";
                case BinaryComparison.LessThan:
                    return "<";
                case BinaryComparison.LessThanOrEqual:
                    return "<=";
                case BinaryComparison.BeginWith:
                case BinaryComparison.EndWith:
                case BinaryComparison.Like:
                    return "like";
                case BinaryComparison.NotLike:
                    return "not like";
                default:
                    throw new NotImplementedException();
            }
        }

        protected virtual string GetOperandClause(Operand operand)
        {
            if (operand is OperandValue)
            {
                return GetOperandClause(operand as OperandValue);
            }
            else if (operand is OperandProperty)
            {
                return GetOperandClause(operand as OperandProperty);
            }
            else if (operand is OperandDecorator)
            {
                return GetOperandClause(operand as OperandDecorator);
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        protected virtual string GetOperandClause(OperandValue operand)
        {
            var clause = Dialect.NamingPattern.GetOperandValueParameterName(operand);
            return clause;
        }

        protected virtual string GetOperandClause(OperandProperty operand)
        {
            var mappingColumn = MainTable.GetMappingColumn(operand.PropertyName);
            var clause = Dialect.NamingPattern.GetColumnNameWithTableAliasInQuote(mappingColumn);
            return clause;
        }

        protected virtual string GetOperandClause(OperandDecorator operand)
        {
            var clause = string.Format(operand.Pattern, GetOperandClause(operand.Value));
            return clause;
        }

        public virtual string GetCriteriaClause(BinaryCriteria criteria)
        {
            switch (criteria.Comparison)
            {
                case BinaryComparison.Like:
                case BinaryComparison.NotLike:
                    return string.Format("({0} {1} {2})",
                        GetOperandClause(criteria.Left),
                        GetComparisonScript(criteria.Comparison),
                        "'%' + " + GetOperandClause(criteria.Right) + " + '%'"
                        );
                case BinaryComparison.BeginWith:
                    return string.Format("({0} {1} {2})",
                        GetOperandClause(criteria.Left),
                        GetComparisonScript(criteria.Comparison),
                        GetOperandClause(criteria.Right) + " + '%'"
                        );
                case BinaryComparison.EndWith:
                    return string.Format("({0} {1} {2})",
                        GetOperandClause(criteria.Left),
                        GetComparisonScript(criteria.Comparison),
                        "'%' + " + GetOperandClause(criteria.Right)
                        );
                default:
                    return string.Format("({0} {1} {2})",
                        GetOperandClause(criteria.Left),
                        GetComparisonScript(criteria.Comparison),
                        GetOperandClause(criteria.Right)
                        );
            }
        }

        public virtual string GetCriteriaClause(BetweenCriteria criteria)
        {
            return string.Format("({0} between {1} and {2})",
                GetOperandClause(criteria.A),
                GetOperandClause(criteria.B),
                GetOperandClause(criteria.C)
                );
        }

        public virtual string GetCriteriaClause(NotCriteria criteria)
        {
            return string.Format("(not {0})",
                GetCriteriaClause(criteria.HostedCriteria)
                );
        }

        public virtual string GetCriteriaClause(NullCriteria criteria)
        {
            return string.Format("({0} is null)",
                GetOperandClause(criteria.Property)
                );
        }

        public virtual string GetCriteriaClause(AndCriteria criteria)
        {
            var notNullList = criteria.CriteriaList.FindAll((a) => a != null);
            if(notNullList.Count() == 0)
            {
                return "";
            }
            else if(notNullList.Count() == 1)
            {
                return GetCriteriaClause(notNullList[0]);
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("(").Append(GetCriteriaClause(criteria.CriteriaList[0]));
                for (int i = 1; i < criteria.CriteriaList.Count; i++)
                {
                    sb.Append(" and ")
                        .Append(GetCriteriaClause(criteria.CriteriaList[i]));
                }
                sb.Append(")");
                return sb.ToString();
            }
        }

        public virtual string GetCriteriaClause(OrCriteria criteria)
        {
            var notNullList = criteria.CriteriaList.FindAll((a) => a != null);
            if (notNullList.Count() == 0)
            {
                return "";
            }
            else if (notNullList.Count() == 1)
            {
                return GetCriteriaClause(notNullList[0]);
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("(").Append(GetCriteriaClause(criteria.CriteriaList[0]));
                for (int i = 1; i < criteria.CriteriaList.Count; i++)
                {
                    sb.Append(" or ")
                        .Append(GetCriteriaClause(criteria.CriteriaList[i]));
                }
                sb.Append(")");
                return sb.ToString();
            }
        }

        public virtual string GetCriteriaClause(InCriteria criteria)
        {
            if(criteria.Values.Count() == 0)
            {
                return "1=1";
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("({0} in (", GetOperandClause(criteria.Field));
            for (int i = 0; i < criteria.Values.Count; i++)
            {
                if (i > 0)
                {
                    sb.Append(", ");
                }
                sb.Append(GetOperandClause(criteria.Values[i]));
            }
            sb.Append("))");
            return sb.ToString();
        }

        public virtual string GetCriteriaClause(ExistsCriteria criteria)
        {
            var innerTable = Dialect.SchemaHelper.CreateInnerTable(criteria.InnerType);
            BaseSqlCreator creator = Dialect.GetSqlCreator(innerTable);

            StringBuilder sb = new StringBuilder();
            sb.Append("(exists (");
            var selectClause = creator.CreateInnerExistsSql(criteria.InnerCriteria);
            sb.Append(selectClause).AppendLine();
            if (criteria.Links != null && criteria.Links.Count() > 0)
            {
                if (criteria.InnerCriteria == null)
                {
                    sb.Append("where ");
                }
                else
                {
                    sb.Append("and ");
                }
                for (int i = 0; i < criteria.Links.Count(); i++)
                {
                    Column mainColumn = MainTable.GetMappingColumn(criteria.Links[i].MainTableProperty);
                    Column innerColumn = innerTable.GetMappingColumn(criteria.Links[i].InnerTableProperty);
                    if(i != 0)
                    {
                        sb.Append("and ");
                    }
                    var mainColumnRef = Dialect.NamingPattern.GetColumnNameWithTableAliasInQuote(mainColumn);
                    var innerColumnRef = Dialect.NamingPattern.GetColumnNameWithTableAliasInQuote(innerColumn);

                    var clause = string.Format("{0} = {1}",
                        mainColumnRef, innerColumnRef);
                    sb.Append(clause).AppendLine();
                }
            }

            sb.Append("))");
            return sb.ToString();
        }

        public virtual string GetCriteriaClause(BaseCriteria criteria)
        {
            if (criteria is BinaryCriteria)
            {
                return GetCriteriaClause(criteria as BinaryCriteria);
            }
            else if (criteria is BetweenCriteria)
            {
                return GetCriteriaClause(criteria as BetweenCriteria);
            }
            else if (criteria is NotCriteria)
            {
                return GetCriteriaClause(criteria as NotCriteria);
            }
            else if (criteria is NullCriteria)
            {
                return GetCriteriaClause(criteria as NullCriteria);
            }
            else if (criteria is AndCriteria)
            {
                return GetCriteriaClause(criteria as AndCriteria);
            }
            else if (criteria is OrCriteria)
            {
                return GetCriteriaClause(criteria as OrCriteria);
            }
            else if (criteria is InCriteria)
            {
                return GetCriteriaClause(criteria as InCriteria);
            }
            else if(criteria is ExistsCriteria)
            {
                return GetCriteriaClause(criteria as ExistsCriteria);
            }
            else
            {
                throw new UnknownCriteriaException(string.Format("Unknown criteria type {0}", criteria.GetType()));
            }
        }
    }

}
